package com.mingyuean.demo.mybatis.binding;

import com.mingyuean.demo.mybatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;

/**
 * @author MingYueAn
 * <p>  映射器代理，实现了InvocationHandler接口
 * <p>  2023/3/12 14:59
 * @version: 1.0
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final Logger log = LoggerFactory.getLogger(MapperProxy.class);

    /**
     * SqlSession对象，负责执行SQL语句
     */
    private final SqlSession sqlSession;
    /**
     * 缓存Mapper接口中的方法和对应的MapperMethod对象
     */
    private final Map<Method, MapperMethod> methodCache;
    /**
     * mapper接口的Class对象
     */
    private final Class<T> mapperInterfaceClass;

    public MapperProxy(SqlSession sqlSession, Map<Method, MapperMethod> methodCache, Class<T> mapperInterfaceClass) {
        this.sqlSession = sqlSession;
        this.methodCache = methodCache;
        this.mapperInterfaceClass = mapperInterfaceClass;
    }

    /**
     * Method接口中invoke方法是动态代理的唯一方法。
     * 在实现时，首先判断要代理的方法是否是Object类直接所有类均包含的方法。
     * 如果是，则直接执行该方法，否则执行该方法对应的MapperMethod对象的execute()方法来执行SQL语句。
     *
     * @param proxy  一般是指代理类
     * @param method 是被代理的方法
     * @param args   为该方法的参数数组
     * @return 对象
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method.getDeclaringClass()：Method对象表示的方法的类的Class对象
        //如果Class是Object.class的话，直接调用该方法，不做其他操作
        //比如：toString()、hashCode()
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            log.debug("方法名 = {}，参数数组args = {}", method.getName(), Arrays.toString(args));
            //获取或创建Method对应的MapperMethod对象，MapperMethod负责SQL的执行
            final MapperMethod mapperMethod = cachedMapperMethod(method);
            //执行SQL语句
            return mapperMethod.execute(sqlSession, args);
        }
    }

    /**
     * 获取或创建Method对应的MapperMethod对象
     *
     * @param method Method对象
     * @return 返回MapperMethod对象
     */
    private MapperMethod cachedMapperMethod(Method method) {
        //先从缓存中获取MapperMethod对象
        MapperMethod mapperMethod = methodCache.get(method);
        //如果没有找到，则创建一个MapperMethod对象
        if (mapperMethod == null) {
            mapperMethod = new MapperMethod(method, mapperInterfaceClass, sqlSession.getConfiguration());
            //将新创建的MapperMethod对象放入缓存
            methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }
}
